//
//  RealmTools.swift
//  RealmSwift
//
//  Created by IronMan on 2020/12/11.
//

import UIKit
import RealmSwift

/**
 官网链接
 https://www.mongodb.com/docs/realm/sdk/swift/
 
 github
 https://github.com/realm/realm-swift
 
 iOS 数据持久化方案-Realm的使用
 https://www.jianshu.com/p/118c1fe1b82f
 
 RealmSwift 详解
 https://www.cnblogs.com/strengthen/p/11166071.html
 
 Realm使用中碰到的问题(坑)及解决方案
 https://www.jianshu.com/p/2075d377f3fb
 
 简介
 https://www.jianshu.com/p/6a1b5903715e
 
 Realm Studio 下载地址
 https://docs.realm.io/sync/realm-studio
 */

/**
 Realm 致力于平衡数据库读取的灵活性和性能。为了实现这个目标，在 Realm 中所存储的信息的各个方面都有基本的限制。例如：
 （1）类名称的长度最大只能存储 57 个 UTF8 字符。
 （2）属性名称的长度最大只能支持 63 个 UTF8 字符。
 （3）NSData 以及 String 属性不能保存超过 16 MB 大小的数据。如果要存储大量的数据，可通过将其分解为16MB 大小的块，或者直接存储在文件系统中，然后将文件路径存储在 Realm 中。如果您的应用试图存储一个大于 16MB 的单一属性，系统将在运行时抛出异常。
 （4）对字符串进行排序以及不区分大小写查询只支持“基础拉丁字符集”、“拉丁字符补充集”、“拉丁文扩展字符集 A” 以及”拉丁文扩展字符集 B“（UTF-8 的范围在 0~591 之间）。
 */

// 事件闭包(做完Realm操作后的事件)
public typealias RealmDoneTask = () -> Void
class RealmTools: NSObject {
    /// 单例
    static let sharedInstance = RealmTools()
    /// 当前的 Realm
    fileprivate var currentRealm: Realm?
    /// 当前realm存储的路径
    static var fileURL: URL? {
        return sharedInstance.currentRealm?.configuration.fileURL
    }
    /// 当前的版本号
    fileprivate var currentSchemaVersion: UInt64 = 0
    /// 当前的加密字符串
    fileprivate var currentKeyWord: String? = ""
}

// MARK: - Realm数据库配置和版本差异化配置
/**
 通过调用 Realm() 来初始化以及访问我们的 realm 变量。其指向的是应用的 Documents 文件夹下的一个名为“default.realm”的文件。
 通过对默认配置进行更改，我们可以使用不同的数据库。比如给每个用户帐号创建一个特有的 Realm 文件，通过切换配置，就可以直接使用默认的 Realm 数据库来直接访问各自数据库
 */
// 在(application:didFinishLaunchingWithOptions:)中进行配置
extension RealmTools {
    
    /// 配置数据库，为用户提供个性化的 Realm 配置(加密暂时没有使用)
    /// - Parameters:
    ///   - tableName: 表名
    ///   - keyWord: 加密字符串
    ///   - schemaVersion: 设置新的架构版本(如果要存储的数据模型属性发生变化)， 这个版本号必须高于之前所用的版本号，如果您之前从未设置过架构版本，那么这个版本号设置为 0）
    static func configRealm(
        tableName: String?, 
        keyWord: String? = nil, 
        schemaVersion: UInt64 = 0, 
        migrationBlock: MigrationBlock? = nil
    ) {
        // 加密串128位结果为：464e5774625e64306771702463336e316a4074487442325145766477335e21346b715169364c406c6a4976346d695958396245346e356f6a62256d2637566126
        // let key: Data = "FNWtb^d0gqp$c3n1j@tHtB2QEvdw3^!4kqQi6L@ljIv4miYX9bE4n5ojb%m&7Va&".data(using: .utf8, allowLossyConversion: false)!
        // 加密的key
        // let key: Data = keyWord.data(using: .utf8, allowLossyConversion: false)!
        // 打开加密文件
        // (encryptionKey: key)
        // 使用默认的目录，但是使用用户 ID 来替换默认的文件名
        // 找到沙盒路径 这里是Document
        let docPath = NSSearchPathForDirectoriesInDomains(.documentDirectory, .userDomainMask, true).first!
        // let fileURL = docPath + "/" + ("\(tableName ?? "")_default.realm")
        let fileURL = docPath + "/" + ("\(tableName ?? "").realm")
        let config = Realm.Configuration(
            fileURL: URL(string: fileURL), 
            encryptionKey: getKey(key: keyWord), 
            schemaVersion: schemaVersion, 
            migrationBlock: { (migration, oldSchemaVersion) in
                // 目前我们还未进行数据迁移，因此 oldSchemaVersion == 0
                if oldSchemaVersion < 1 {
                    // 什么都不要做！Realm 会自行检测新增和需要移除的属性，然后自动更新硬盘上的数据库架构
                }
                // 低版本的数据库迁移......
                if migrationBlock != nil {
                    migrationBlock!(migration, oldSchemaVersion)
                }
            })
        // 告诉 Realm 为默认的 Realm 数据库使用这个新的配置对象
        Realm.Configuration.defaultConfiguration = config
        guard let realm = try? Realm(configuration: config) else {
            return
        }
        sharedInstance.currentSchemaVersion = schemaVersion
        sharedInstance.currentRealm = realm
        sharedInstance.currentKeyWord = keyWord
    }
    
    /// 删除当前的realm库
    @discardableResult
    static func deleteRealmFiles() -> Bool {
        let realmURL = sharedInstance.currentRealm?.configuration.fileURL ?? Realm.Configuration.defaultConfiguration.fileURL!
        let realmURLs = [
            realmURL,
            realmURL.appendingPathExtension("lock"),
            realmURL.appendingPathExtension("management")
        ]
        for URL in realmURLs {
            do {
                try FileManager.default.removeItem(at: URL)
                self.configRealm(tableName: nil, keyWord: sharedInstance.currentKeyWord, schemaVersion: sharedInstance.currentSchemaVersion)
            } catch {
                // handle error
                return false
            }
        }
        return true
    }
}

// MARK: - 增
extension RealmTools {
    
    /// 添加单个对象
    /// - Parameters:
    ///   - object: 对象
    ///   - update: 是否更新
    ///   - task: 添加后操作
    static func add(
        _ object: Object, 
        update: Realm.UpdatePolicy = .error, 
        _ task: RealmDoneTask? = nil
    ) {
        guard let weakCurrentRealm = sharedInstance.currentRealm else {
            return
        }
        try? weakCurrentRealm.write {
            weakCurrentRealm.add(object, update: update)
            task?()
        }
    }
    
    /// 添加多个对象
    /// - Parameters:
    ///   - objects: 对象组
    ///   - update: 是否更新
    ///   - task: 添加后操作
    static func add(
        _ objects: Array<Object>, 
        update: Realm.UpdatePolicy = .error, 
        _ task: RealmDoneTask? = nil
    ) {
        guard let weakCurrentRealm = sharedInstance.currentRealm else {
            return
        }
        try? weakCurrentRealm.write {
            weakCurrentRealm.add(objects, update: update)
            task?()
        }
    }
}

// MARK: - 删
extension RealmTools {
    
    /// 在事务中删除一个对象
    /// - Parameters:
    ///   - object: 单个被删除的对象
    ///   - task: 删除后操作
    static func delete(
        _ object: Object, 
        _ task: RealmDoneTask? = nil
    ) {
        guard let weakCurrentRealm = sharedInstance.currentRealm else {
            return
        }
        try? weakCurrentRealm.write {
            weakCurrentRealm.delete(object)
            task?()
        }
    }
    
    /// 在事务中删除多个对象
    /// - Parameters:
    ///   - objects: 多个要被删除的对象
    ///   - task: 删除后操作
    static func delete(
        _ objects: Array<Object>, 
        _ task: RealmDoneTask? = nil
    ) {
        guard let weakCurrentRealm = sharedInstance.currentRealm else {
            return
        }
        try? weakCurrentRealm.write {
            weakCurrentRealm.delete(objects)
            task?()
        }
    }
    
    /// 从 Realm 中删除所有数据（不要轻易调用）
    /// - Parameter task: 删除后操作
    static func deleteAll(_ task: RealmDoneTask? = nil) {
        guard let weakCurrentRealm = sharedInstance.currentRealm else {
            return
        }
        try? weakCurrentRealm.write {
            weakCurrentRealm.deleteAll()
            task?()
        }
    }
    
    /// 根据条件删除对象
    /// - Parameters:
    ///   - object: 对象类型
    ///   - predicate: 条件
    static func delete(
        _ object: Object.Type, 
        predicate: NSPredicate
    ) {
        guard let results: Array<Object> = find(object, predicate: predicate) else {
            return
        }
        delete(results)
    }
    
    /// 根据条件删除对象
    /// - Parameters:
    ///   - object: 对象类型
    ///   - key: 查询的字段名
    ///   - value: 查询的值
    static func delete(
        _ object: Object.Type, 
        key: String, 
        value: String
    ) {
        // 查询条件
        let predicate = NSPredicate(format: "\(key) == \(value)", argumentArray: nil)
        delete(object, predicate: predicate)
    }
    
}

// MARK: - 改
extension RealmTools {
    
    /// 更改某个对象（根据主键存在来更新，元素必须有主键）
    /// - Parameters:
    ///   - object: 某个对象
    ///   - update: 是否更新
    static func update(
        _ object: Object, 
        update: Realm.UpdatePolicy = .modified
    ) {
        guard let weakCurrentRealm = sharedInstance.currentRealm else {
            return
        }
        try? weakCurrentRealm.write {
            weakCurrentRealm.add(object, update: update)
        }
    }
    
    /// 更改多个对象（根据主键存在来更新，元素必须有主键）
    /// - Parameters:
    ///   - objects: 多个对象
    ///   - update: 是否更新
    static func update(
        _ objects: Array<Object>, 
        update: Realm.UpdatePolicy = .modified
    ) {
        guard let weakCurrentRealm = sharedInstance.currentRealm else {
            return
        }
        try? weakCurrentRealm.write {
            weakCurrentRealm.add(objects, update: .modified)
        }
    }
    
    /// 更新操作，对于realm搜索结果集当中的元素，
    /// 在action当中直接赋值即可修改(比如查询到的某些属性可直接修改)
    /// - Parameter action: 操作
    static func update(_ action: () -> Void) {
        guard let weakCurrentRealm = sharedInstance.currentRealm else {
            return
        }
        try? weakCurrentRealm.write {
            action()
        }
    }
    
    /// 更新一个一个对象的多个属性值（根据主键存在来更新，元素必须有主键）
    /// - Parameters:
    ///   - object: 对象类型
    ///   - value: 数组数组 value: Any = [K: V]
    ///   - update: 更新类型
    static func update(
        _ object: Object.Type, 
        value: Any, 
        update: Realm.UpdatePolicy = .modified
    ) {
        guard let weakCurrentRealm = sharedInstance.currentRealm else {
            return
        }
        do {
            try weakCurrentRealm.write {
                weakCurrentRealm.create(object, value: value, update: update)
            }
        } catch _ {
            
        }
    }
}

// MARK: - 查
extension RealmTools {
    /// 查询某个对象数据
    /// - Parameter type: 对象类型
    /// - Returns: 返回查询的结果
    static func find(_ object: Object.Type) -> Array<Object>? {
        guard let results = queryWithType(object: object) else {
            return nil
        }
        return resultsToObjectList(results: results)
    }
    
    /// 查询某个对象数据(根据条件)
    /// - Parameters:
    ///   - object: 对象类型
    ///   - predicate: 查询条件
    /// - Returns: 返回查询结果
    static func find(
        _ object: Object.Type, 
        predicate: NSPredicate
    ) -> Array<Object>? {
        guard let results = queryWith(object: object, predicate: predicate) else {
            return nil
        }
        return resultsToObjectList(results: results)
    }
    
    /// 查询某个对象数据(根据条件)
    /// - Parameters:
    ///   - object: 对象类型
    ///   - key: 查询的字段名
    ///   - value: 查询的值
    /// - Returns: 返回查询结果
    static func find(
        _ object: Object.Type, 
        key: String, 
        value: String
    ) -> Array<Object>? {
        // 查询条件
        let predicate = NSPredicate(format: "\(key) == \(value)", argumentArray: nil)
        return find(object, predicate: predicate)
    }
    
    ///  带排序条件查询
    /// - Parameters:
    ///   - object: 对象类型
    ///   - predicate:  查询条件
    ///   - sortedKey: 排序的键
    ///   - isAssending: true:升序 false:降序，默认:升序
    /// - Returns: 返回查询对象数组
    static func find(
        _ object: Object.Type,
        predicate: NSPredicate,
        sortedKey: String,
        isAssending: Bool = true
    ) -> Array<Object>? {
        guard let results = queryWithSorted(object: object, predicate: predicate, sortedKey: sortedKey, isAssending: isAssending) else {
            return nil
        }
        return resultsToObjectList(results: results)
    }
    
    /// 带分页的查询
    /// - Parameters:
    ///   - object: 对象类型
    ///   - predicate: 查询条件
    ///   - sortedKey: 排序的键
    ///   - isAssending: 升序还是降序，默认升序
    ///   - fromIndex: 起始页
    ///   - pageSize: 一页的数量
    /// - Returns: 返回查询对象数组
    static func find(
        _ object: Object.Type,
        predicate: NSPredicate,
        sortedKey: String,
        isAssending: Bool,
        fromIndex: Int,
        pageSize: Int
    ) -> Array<Object>? {
        guard let results = queryWithSorted(
            object: object,
            predicate: predicate,
            sortedKey: sortedKey,
            isAssending: isAssending
        ) else {
            return nil
        }
        var resultsArray = Array<Object>()
        if results.count <= pageSize * (fromIndex - 1) || fromIndex <= 0 {
            return resultsArray
        }
        if results.count > 0 {
            for i in pageSize * (fromIndex - 1)...(fromIndex * pageSize - 1) {
                resultsArray.append(results[i])
            }
        }
        return resultsArray
    }
}

// MARK: - 私有(查询)
extension RealmTools {
    
    /// 查询某个对象数据
    /// - Parameter object: 对象类型
    /// - Returns: 返回查询对象数组
    private static func queryWithType(object: Object.Type) -> Results<Object>? {
        guard let weakCurrentRealm = sharedInstance.currentRealm else {
            return nil
        }
        return weakCurrentRealm.objects(object)
    }
    
    /// 根据条件查询数据
    /// - Parameters:
    ///   - object: 对象类型
    ///   - predicate: 查询条件
    /// - Returns: 返回查询对象数组
    private static func queryWith(
        object: Object.Type, 
        predicate: NSPredicate
    ) -> Results<Object>? {
        guard let weakCurrentRealm = sharedInstance.currentRealm else {
            return nil
        }
        return weakCurrentRealm.objects(object).filter(predicate)
    }
    
    /// 带排序条件查询
    /// - Parameters:
    ///   - object: 对象类型
    ///   - predicate: 查询条件
    ///   - sortedKey: 排序的键
    ///   - isAssending: 升序还是降序，默认升序
    /// - Returns: 返回查询对象数组
    private static func queryWithSorted(
        object: Object.Type, 
        predicate: NSPredicate, 
        sortedKey: String, 
        isAssending: Bool = true
    ) -> Results<Object>? {
        guard let weakCurrentRealm = sharedInstance.currentRealm else {
            return nil
        }
        return weakCurrentRealm.objects(object).filter(predicate)
            .sorted(byKeyPath: sortedKey, ascending: isAssending)
    }
    
    /// 查询结果转Array<Object>
    /// - Parameter results: 查询结果
    /// - Returns: 返回Array<Object>
    private static func resultsToObjectList(results: Results<Object>) -> Array<Object> {
        var resultsArray = Array<Object>()
        if results.count > 0 {
            for i in 0...(results.count - 1) {
                resultsArray.append(results[i])
            }
        }
        return resultsArray
    }
}

extension RealmTools {
    static func getKey(key: String?) -> Data? {
        if key == nil {
            return nil
        }
        // Identifier for our keychain entry - should be unique for your application
        // 我们的钥匙链入口的标识符-对于您的应用程序应该是唯一的
        let keychainIdentifier = key! //"io.Realm.EncryptionExampleKey"
        let keychainIdentifierData = keychainIdentifier.data(using: String.Encoding.utf8, allowLossyConversion: false)!
        
        // First check in the keychain for an existing key
        // 首先在keychain中检查现有的密钥
        var query: [NSString: AnyObject] = [
            kSecClass: kSecClassKey,
            kSecAttrApplicationTag: keychainIdentifierData as AnyObject,
            kSecAttrKeySizeInBits: 512 as AnyObject,
            kSecReturnData: true as AnyObject
        ]
        
        // To avoid Swift optimization bug, should use withUnsafeMutablePointer() function to retrieve the keychain item
        // 为了避免Swift优化错误，应该使用withUnsafeMutablePointer()函数来检索钥匙链项
        // See also: http://stackoverflow.com/questions/24145838/querying-ios-keychain-using-swift/27721328#27721328
        var dataTypeRef: AnyObject?
        var status = withUnsafeMutablePointer(to: &dataTypeRef) { SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0)) }
        if status == errSecSuccess {
            return dataTypeRef as? Data
        }
        
        // No pre-existing key from this application, so generate a new one
        // 此应用程序中没有预先存在的密钥，因此生成一个新密钥
        let keyData = NSMutableData(length: 64)!
        let result = SecRandomCopyBytes(kSecRandomDefault, 64, keyData.mutableBytes.bindMemory(to: UInt8.self, capacity: 64))
        assert(result == 0, "Failed to get random bytes")
        
        // Store the key in the keychain
        // 将钥匙存储在钥匙链中
        query = [
            kSecClass: kSecClassKey,
            kSecAttrApplicationTag: keychainIdentifierData as AnyObject,
            kSecAttrKeySizeInBits: 512 as AnyObject,
            kSecValueData: keyData
        ]
        
        status = SecItemAdd(query as CFDictionary, nil)
        assert(status == errSecSuccess, "Failed to insert the new key in the keychain")
        
        return keyData as Data
    }
}
